import socket
import logging
import serial
import threading

default_tcp_port = 3001
default_tcp_server = "127.0.0.1"
#default_tcp_server = "192.168.9.20"

# ------------------------------------------------------
#  TCP CMD SERVER
# ------------------------------------------------------

class TcpCmdServer:
    def __init__(self,tcp_port=default_tcp_port,serial_port=None,serial_baud=38400):
        self.sock = None
        self.thread = None
        self.clients = None
        self.serial = None

        self.tcp_port = tcp_port
        self.serial_port = serial_port
        self.serial_baud = serial_baud

        self.serial_lock = threading.Lock()

    def open(self):

        # clear everything first
        self.close()

        # open TCP port
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.bind(("0.0.0.0",self.tcp_port))
            sock.listen(1)
        except:
            logging.error("Failed: opening TCP port %s" % self.tcp_port)
            self.close()
            return False

        # open serial port
        try:
            self.serial = serial.Serial(self.serial_port,self.serial_baud)
        except:
            logging.error("Failed: opening serial port %s" % self.serial_port)
            self.close()
            return False

        self.sock = sock
        self.clients = {}
        return True

    def _ThreadConn(self, conn, addr):

        # log new connection
        logging.info("TCPConn: handling %s on port %s", addr[0], addr[1])

        buff = str()
        while True:
            # receive data until endline
            data = conn.recv(20)
            if not data: break

            buff += data
            if "\n" not in data:
                continue

            # extract command out of buffer
            index = buff.find('\n')
            inp   = buff[:index]
            buff  = buff[index + 1:]

            inp = inp.strip(' \n\r')

            # ignore empty inputs
            if len(inp) == 0:
                logging.info("[%s]Command:%s", addr, "<empty>")
                continue

            logging.info("[%s]Command:%s", addr, inp)

            # lock serial connection
            self.serial_lock.acquire()
            serout = ""

            try:
                # discard any other input bytes
                while self.serial.inWaiting():
                    self.serial.read()

                # write received data to serial
                self.serial.write(inp + "\n")

                logging.info("[%s]Waiting response..", addr)

                # read data from serial until '>' is received
                while True:
                    data = self.serial.read()
                    if data == ">":
                        conn.send(serout + ">")
                        break

                    serout += data

                # print response
                logging.info("[%s]Reponse:%s", addr, serout)

            except:
                # any exception will trigger empty response
                logging.info("[%s]Exception during serial comm", addr)
                # conn.send(">")

            # release serial lock
            self.serial_lock.release()

        logging.info("[%s]Done", addr)

        # log connection lost/close
        logging.info("TCPConn: ending with %s on port %s", addr[0], addr[1])

        del self.clients[threading.current_thread()]
        conn.close()

    def _ThreadSrv(self):

        while True:
            try:
                # wait for connection
                conn, addr = self.sock.accept()
                logging.info("TCP server: New connection %s", addr)
            except:
                return

            # start a new thread for each connection
            clientThread = threading.Thread(
                target=TcpCmdServer._ThreadConn,
                args=(self, conn, addr))

            # push back list of clients
            self.clients[clientThread] = conn

            # start thread
            clientThread.start()

    def run(self):

        if self.thread != None or self.sock == None:
            return False

        # start thread
        thread = threading.Thread(target=TcpCmdServer._ThreadSrv,args=(self,))
        thread.start()

        self.thread = thread
        return True

    def close(self):

        # close server socket
        if self.sock != None:
            self.sock.close()
            self.sock = None

        # server thread
        self.thread = None

        # client sockets
        if self.clients != None:
            for x in self.clients.iteritems():
                x[1].close()

        self.clients = None

# ------------------------------------------------------
# TCP CMD CLIENT
# ------------------------------------------------------

class TcpCmdClient:
    def __init__(self,tcp_server=default_tcp_server,tcp_port=default_tcp_port,auto_open=True):
        self.sock = None
        self.tcp_server = tcp_server
        self.tcp_port = tcp_port
        self.error = None

        if auto_open != False:
            self.open()

    def open(self):

        # close everything else
        self.close()

        # create socket
        try:
            sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
            sock.connect( (self.tcp_server, self.tcp_port) )
        except BaseException as e:
            self.error = e.message
            return False

        # success
        self.sock = sock

    def command(self, command):
        if self.sock == None:
            return None

        # prepare data
        self.error = None
        response = ""

        try:
            # send command
            self.sock.send(command+"\n")

            # receive response
            while True:
                data = self.sock.recv(1)
                if not data:
                    self.close()
                    return None

                if data == ">":
                    break
                response += data

        except BaseException as e:
            self.error = e.message
            return None

        # success
        return response


    def close(self):
        self.error = None

        # close socket if existent
        if self.sock != None:
            self.sock.close()
            self.sock = None

def runCommand(server,port,command):
    client = TcpCmdClient(tcp_server=server, tcp_port=port);

    if client.open() == False:
        return None

    response = client.command(command)
    client.close()
    return response

# ------------------------------------------------------
# MAIN
# ------------------------------------------------------

if __name__ == "__main__":
    server = TcpCmdServer(serial_port="\\\\.\\COM1")
    logging.getLogger().setLevel(logging.INFO)
    server.open()
    if server.run():
        server.thread.join()
